using System;
using System.Collections.Generic;
using System.Linq;
using MathematicallySafe.HeadCounter.Data;
using MathematicallySafe.HeadCounter.Data.Interfaces;
using MathematicallySafe.HeadCounter.Data.Models;
using StructureMap;

namespace MathematicallySafe.HeadCounter.Services.Calculators
{
    public abstract class PeriodicCalculation: Processor<Raider>
    {
        private readonly IRaidHistory RaidHistory = ObjectFactory.GetInstance<IRaidHistory>();

        private readonly int _shortPeriod;
        private readonly int _longPeriod;
        private readonly int _lifetimePeriod;
        private readonly int _endWeek;

        private readonly Dictionary<int, IEnumerable<Raid>> _raidingPeriods = new Dictionary<int, IEnumerable<Raid>>();

        public PeriodicCalculation(int shortPeriod, int longPeriod)
        {
            _shortPeriod = shortPeriod;
            _longPeriod = longPeriod;
            _lifetimePeriod = longPeriod + 1;
            _endWeek = RaidHistory.LatestRaidWeek;
        }

        public PeriodicCalculation(int shortPeriod, int longPeriod, int endWeek)
        {
            _shortPeriod = shortPeriod;
            _longPeriod = longPeriod;
            _lifetimePeriod = longPeriod + 1;
            _endWeek = endWeek;
        }

        protected override void Initialise()
        {
            RecordRaidPeriods(_shortPeriod);
            RecordRaidPeriods(_longPeriod);
            RecordRaidPeriods(_lifetimePeriod);
        }

        private void RecordRaidPeriods(int periodWeeks)
        {
            var raids = periodWeeks == _lifetimePeriod ? GetAllRaids() : GetRaids(periodWeeks);
            _raidingPeriods.Add(periodWeeks, raids);
        }

        private IOrderedEnumerable<Raid> GetRaids(int weeks)
        {
            IOrderedEnumerable<Raid> history = from r in RaidHistory.Raids
                                               where r.WeekNumber >= _endWeek - weeks
                                               orderby r.Date
                                               select r;

            return history;
        }

        private IOrderedEnumerable<Raid> GetAllRaids()
        {
            IOrderedEnumerable<Raid> history = from r in RaidHistory.Raids
                                               orderby r.Date
                                               select r;

            return history;
        }

        protected IEnumerable<Raid> ShortRaidingPeriod
        {
            get { return _raidingPeriods[_shortPeriod]; }
        }

        protected IEnumerable<Raid> LongRaidingPeriod
        {
            get { return _raidingPeriods[_longPeriod]; }
        }

        protected IEnumerable<Raid> LifetimeRaidingPeriod
        {
            get { return _raidingPeriods[_lifetimePeriod]; }
        }

        protected IEnumerable<Raid> GetAdjustedRaidingPeriod(Raider raider, IEnumerable<Raid> raidingPeriod, bool includingOptionalRaids)
        {
            IEnumerable<Raid> adjustedPeriod = raidingPeriod;

            var start = raidingPeriod.First().Date;

            if (raider.Joined > start)
            {
                // We just want the raids that this raider could have attended in the 
                // designated period.
                // i.e. if you attend your first raid attendance is 100% not a percentage
                // of raids that happenend before you joined.

                adjustedPeriod = raidingPeriod.Where(r => r.Date >= raider.Joined);
            }

            if (!includingOptionalRaids)
            {
                adjustedPeriod = adjustedPeriod.Where(r => !r.IsOptional);
            }

            return adjustedPeriod;
        }

    }
}